/******************************************************************************/

package jamie.org.sil.www.usxUtils;

import java.io.*;
import java.util.zip.*;
import java.util.zip.CRC32.*;



/******************************************************************************/
/**
   A ZIP file handler.
*/

public class Zip
{
  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                               Public                                   **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  /**
     Zips one or more files or folders, retaining structure information.

     @param zipFilePath Path for output zip file.

     @param compressionLevel Compression level.  If negative, the inbuilt
                             default is used.

     @param relativeTo Path relative to which relative paths are determined.
                       Take care to ensure this genuinely is a prefix of all
                       the individual input files.

     @param inputs Input files or folders.  Each may optionally be terminated
                   by "¬" followed by something starting S(tored) or
                   D(eflated) to determine the compression to be applied.
                   If this is not supplied, Deflated is assumed.

     @throws UsfmException
  */

  public void create (String zipFilePath,
                      int compressionLevel,
                      String relativeTo,
                      String inputs[]) throws ConverterException
  {
    try
    {
      FileOutputStream dest = new FileOutputStream(zipFilePath);
      m_ZipStream = new ZipOutputStream(new BufferedOutputStream(dest));
      if (compressionLevel >= 0) m_ZipStream.setLevel(compressionLevel);

      if (0 != relativeTo.length()) relativeTo = (new File(relativeTo)).getCanonicalPath();

      for (int i = 0; i < inputs.length; ++i)
        zip(relativeTo, inputs[i]);

      m_ZipStream.close();
    }
    catch (Exception e)
    {
      throw new ConverterException(ConverterException.Action.AbandonRunNow, ConverterException.Log.Error, "Zip failed");
    }
  }



  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                               Private                                  **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  private ZipOutputStream m_ZipStream = null;


  /****************************************************************************/
  /* Processes a single input -- splits out and applies any compression
     setting, and then passes the input on for further processing. */

  private void zip (String relativeTo, String path) throws Exception
  {
    String[] details = path.split("¬");
    int method = 0;

    if (2 == details.length)
      method = details[1].toUpperCase().startsWith("S") ? ZipOutputStream.STORED : ZipOutputStream.DEFLATED;
    else
      method = ZipOutputStream.DEFLATED;

    if (0 != details[0].length()) zip1(relativeTo, details[0], method);
  }


  /****************************************************************************/
  /* Processes a single folder or file. */

  private void zip1 (String relativeTo, String path, int method) throws IOException
  {
    String absolutePath = (0 == relativeTo.length() ? "" : relativeTo + File.separator) + path;
    if ((new File(absolutePath)).isFile())
      zipFile(relativeTo, path, absolutePath, method);
    else
    {
      File f = new File(absolutePath);
      String files[] = f.list();
      for (int i = 0; i < files.length; ++i)
        zip1(relativeTo, absolutePath.substring(1 + relativeTo.length()) + File.separator + files[i], method);
    }
  }


  /****************************************************************************/
  /* Processes a single file.  Note that when I set up the ZipEntry structure,
     I force the relative path to use "/" as a folder separator, rather than
     "\".  I'm not sure whether this is always necessarily the correct thing
     to do, but if you're using the stuff here to produce ePub files, the
     Calibre reader would only read a file once before it started complaining
     about mismatches between "/" and "\". */

  private void zipFile (String relativeTo,
                        String relativePath,
                        String absolutePath,
                        int method) throws IOException
  {
    final int C_BufLen = 65536;
    int count = 0;
    byte data[] = new byte[C_BufLen];
    ZipEntry entry = new ZipEntry(relativePath.replace(File.separator, "/"));
    entry.setMethod(method);

    if (ZipOutputStream.STORED == method)
    {
      entry.setSize((new File(absolutePath).length()));
      CRC32 crc = new CRC32();
      FileInputStream fi = new FileInputStream(absolutePath);
      BufferedInputStream source = new BufferedInputStream(fi, C_BufLen);
      while((count = source.read(data, 0, C_BufLen)) != -1) crc.update(data, 0, count);
      source.close();
      fi.close();
      entry.setCrc(crc.getValue());
    }

    m_ZipStream.putNextEntry(entry);

    FileInputStream fi = new FileInputStream(absolutePath);
    BufferedInputStream source = new BufferedInputStream(fi, C_BufLen);
    while((count = source.read(data, 0, C_BufLen)) != -1) m_ZipStream.write(data, 0, count);
    source.close();
    fi.close();
  }
}
